/*
 * Copyright (c) 2025 Li Auto Inc. and its affiliates
 * Licensed under the Apache License, Version 2.0(the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <pthread.h>
#include <stdlib.h>
#include <ctype.h>
#include <signal.h>
#include <net/if.h>
#include <linux/if_tun.h>
#include <sys/ioctl.h>
#include <linux/sockios.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <syslog.h>

#include "config.h"
#include "sim_tapdev_posix.h"

#define CONFIG_SIM_NETDEV_MTU (1500)
#define SIM_ETH_CMD_LEN (64)
#define TAP_DEVICE_PATH "/dev/net/tun"

extern void veth_print(int priority, const char *fmt, ...);

static inline void init_ifreq(struct ifreq *ifr, const char *name);
static int add_tap_to_bridge(int sockfd, char *tap_name);

static inline void init_ifreq(struct ifreq *ifr, const char *name)
{
    memset(ifr, 0, sizeof(*ifr));
    snprintf(ifr->ifr_name, IFNAMSIZ, "%s", name);
}

static int add_tap_to_bridge(int sockfd, char *tap_name)
{
    char cmd[SIM_ETH_CMD_LEN];
    struct ifreq ifr;
    unsigned int tap_index;

    init_ifreq(&ifr, CONFIG_SIM_BRIDGE_NAME);

    tap_index = if_nametoindex(tap_name); /* Get the index of the TAP device */
    if (tap_index == 0) {
        veth_print(LOG_DEBUG, "[tap] Get Index of Tap Failed: %s\n", tap_name);
        return -1;
    }
    ifr.ifr_ifindex = tap_index;

    if (ioctl(sockfd, SIOCBRADDIF, &ifr) < 0) { /* Perform an add operation on the bridge  */
        veth_print(LOG_DEBUG, "[tap] Failed to add %s to bridge: %s\n", tap_name, strerror(errno));
        return -1;
    }

    /* Add the TAP device to the VLAN filter table of the bridge, allowing VLAN ID 1-4094 */
    snprintf(cmd, sizeof(cmd), "bridge vlan add dev %s vid 1-4094", tap_name);

    if (0 != system(cmd)) {
        veth_print(LOG_DEBUG, "[tap] Failed to execute bridge command\n");
        return -1;
    }

    snprintf(cmd, sizeof(cmd), "bridge vlan add dev %s vid 1 pvid untagged", tap_name);
    if (0 != system(cmd)) {
        veth_print(LOG_DEBUG, "[tap] Failed to execute bridge command\n");
        return -1;
    }

    return 0;
}

int tap_create(char *tap_name, int *fd_tap)
{
    struct ifreq ifr;
    int fd, sockfd, err;
    char actual_tap_name[IFNAMSIZ]; /* Save the actual interface name */

    fd = open(TAP_DEVICE_PATH, O_RDWR);
    if (fd < 0) {
        veth_print(LOG_DEBUG, "[tap] Failed to open %s\n", TAP_DEVICE_PATH);
        return -1;
    }
    /* Configure TAP device */
    init_ifreq(&ifr, tap_name);
    ifr.ifr_flags = IFF_TAP | IFF_NO_PI;

    err = ioctl(fd, TUNSETIFF, &ifr);
    if (err < 0) {
        veth_print(LOG_DEBUG, "[tap] ioctl(TUNSETIFF) failed\n");
        close(fd);
        return -1;
    }

    sockfd = socket(AF_INET, SOCK_DGRAM, 0); /* Creating a Network Socket */
    if (sockfd < 0) {
        veth_print(LOG_DEBUG, "[tap] Socket creation failed\n");
        close(fd);
        return -1;
    }

    snprintf(actual_tap_name, IFNAMSIZ, "%s", ifr.ifr_name);

    init_ifreq(&ifr, actual_tap_name);
    ifr.ifr_mtu = CONFIG_SIM_NETDEV_MTU;
    if (ioctl(sockfd, SIOCSIFMTU, &ifr) < 0) { /* Set MTU */
        veth_print(LOG_DEBUG, "[tap] Set MTU failed\n");
        close(sockfd);
        close(fd);
        return -1;
    }

    init_ifreq(&ifr, actual_tap_name);
    if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) < 0) {
        veth_print(LOG_DEBUG, "[tap] Get flags failed\n");
        close(sockfd);
        close(fd);
        return -1;
    }

    ifr.ifr_flags |= IFF_UP;
    if (ioctl(sockfd, SIOCSIFFLAGS, &ifr) < 0) {
        veth_print(LOG_DEBUG, "[tap] Set UP failed\n");
        close(sockfd);
        close(fd);
        return -1;
    }

    if (0 != add_tap_to_bridge(sockfd, actual_tap_name)) {
        close(sockfd);
        close(fd);
        return -1;
    }
    *fd_tap = fd;
    close(sockfd);
    return 0;
}

ssize_t tap_write(int fd, const void *buf, size_t count)
{
    return write(fd, buf, count);
}

ssize_t tap_read(int fd, void *buf, size_t count)
{
    return read(fd, buf, count);
}

int tap_get_mode(int tap_fd, int *ctrl_mode_ptr)
{
    struct ifreq ifr;
    int sockfd;

    memset(&ifr, 0, sizeof(ifr));

    if (ioctl(tap_fd, TUNGETIFF, &ifr) < 0) { /* Use the TUNGETIFF command to get the interface name */
        veth_print(LOG_DEBUG, "[tap] ioctl(TUNGETIFF) failed\n");
        return -1;
    }
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        veth_print(LOG_DEBUG, "[tap] socket() failed\n");
        return -1;
    }

    if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) < 0) { /* Get status (UP/DOWN) */
        veth_print(LOG_DEBUG, "[tap] ioctl(SIOCGIFFLAGS) failed\n");
        close(sockfd);
        return -1;
    }
    *ctrl_mode_ptr = (ifr.ifr_flags & IFF_UP) ? 1 : 0;
    close(sockfd);
    return 0;
}

int tap_set_mode(int tap_fd, int ctrl_mode)
{
    struct ifreq ifr;
    int ret;
    int sockfd;

    memset(&ifr, 0, sizeof(ifr));

    if (ioctl(tap_fd, TUNGETIFF, &ifr) < 0) { /* Use the TUNGETIFF command to get the interface name */
        veth_print(LOG_DEBUG, "[tap] ioctl(TUNGETIFF) failed\n");
        return -1;
    }
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        veth_print(LOG_DEBUG, "[tap] get socket fd failed\n");
        return -1;
    }
    if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) < 0) {
        veth_print(LOG_DEBUG, "[tap] ioctl(SIOCGIFFLAGS) failed\n");
        close(sockfd);
        return -1;
    }
    if (1 == ctrl_mode) {
        ifr.ifr_flags |= IFF_UP;
    } else if (0 == ctrl_mode) {
        ifr.ifr_flags &= ~IFF_UP;
    } else {
        veth_print(LOG_DEBUG, "[tap] param ctrl_mode invalid: %d\n", ctrl_mode);
        close(sockfd);
        return -1;
    }
    ret = ioctl(sockfd, SIOCSIFFLAGS, (unsigned long)&ifr);
    if (ret < 0) {
        veth_print(LOG_DEBUG, "[tap] ioctl(SIOCSIFFLAGS) failed\n");
        close(sockfd);
        return -1;
    }
    close(sockfd);
    return 0;
}
